package com.mlnx.push.websocket.server;

import com.mlnx.common.utils.MyLog;
import com.mlnx.device.session.SessionManagerFactory;
import com.mlnx.push.websocket.server.handler.EventHandle;
import com.mlnx.push.websocket.server.handler.PushHandle;
import com.mlnx.push.websocket.server.handler.PushServerHandle;
import com.mlnx.push.websocket.server.handler.PushWebEncode;
import com.mlnx.push.websocket.server.handler.RegisterHandle;
import com.mlnx.push.websocket.server.handler.SubscribeHandle;
import com.mlnx.push.websocket.server.handler.WebSocketFrameHandler;

import org.shan.mq.session.core.Session;
import org.shan.mq.session.core.SessionManager;

import java.util.Date;
import java.util.List;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;

/**
 * Created by amanda.shan on 2019/10/31.
 */
public class WebsocketPushNettyServer {

    private MyLog log = MyLog.getLog(getClass());

    private static final String WEBSOCKET_PATH = "/";

    static final boolean SSL = false;

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup();

    protected SessionManager sessionManager = SessionManagerFactory.getInstance().getPushSessionManager();

    public void start(boolean sync) throws Exception {
        start(1883, sync);
    }

    public void start(int port, boolean sync) throws Exception {

        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup);
        b.option(ChannelOption.SO_BACKLOG, 1024);
        b.channel(NioServerSocketChannel.class);
        b.handler(new LoggingHandler());
        b.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {

                if (sslCtx != null) {
                    ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));
                }

                ch.pipeline().addLast(new EventHandle());

                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new HttpObjectAggregator(65536));
                ch.pipeline().addLast(new WebSocketServerCompressionHandler());
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, "push", true){
                    @Override
                    protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception {
                        super.decode(ctx, frame, out);

                        if (frame instanceof PingWebSocketFrame) {
                            Session session = sessionManager.getSession(ctx.channel());
                            if (session != null) {
                                session.setLastPacketTime(new Date());
                            }
                        }
                    }
                });
                ch.pipeline().addLast(new WebSocketFrameHandler());
                ch.pipeline().addLast(new PushWebEncode());

                ch.pipeline().addLast(new PushServerHandle());
                ch.pipeline().addLast(new RegisterHandle());
                ch.pipeline().addLast(new SubscribeHandle());
                ch.pipeline().addLast(new PushHandle());

            }
        });

        ChannelFuture f = b.bind(port).sync();
        log.info("mq service start OK bind port: {}", port);

        if (sync) {
            f.channel().closeFuture().sync();
        }
    }

    public static void main(String[] args) throws Exception {

        new WebsocketPushNettyServer().start(1999, true);
    }
}
